﻿using net.xBei.Helper;
using net.xBei.Models;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc.Filters;
using System;
using System.Threading.Tasks;
using System.Collections.Concurrent;
using MySqlConnector;
using System.Security.Claims;
using net.xBei.WebApi.Repository;
using net.xBei.WebApi.Helper;

namespace net.xBei.WebApi.FrameworkExtensions {
    /// <summary>
    /// 
    /// </summary>
    public class BackendUserHandler : AuthorizationHandler<BackendUserHandler.BackendRequirement> {
        /// <inheritdoc/>
        protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, BackendRequirement requirement) {
            if (context == null) { return Task.CompletedTask; }
            if (context.User.HasClaim(m => m.Type == AuthorizationHandler.ClaimUserType)) {
                var userType = int.TryParse(context.User.FindFirstValue(AuthorizationHandler.ClaimUserType), out var v) ? v : 0;
                if (userType.GetEnum(CommonConsts.UserType.GUEST).HasFlag(CommonConsts.UserType.BACKEND)) {
                    context.Succeed(requirement);
                }
            }
            return Task.CompletedTask;
        }

        /// <summary>
        /// 
        /// </summary>
        public class BackendRequirement : IAuthorizationRequirement {
        }
    }
    /// <summary>
    /// 系统用户（内部调用）认证
    /// </summary>
    public class SystemUserHandler : AuthorizationHandler<SystemUserHandler.SystemUserRequirement> {
        /// <inheritdoc/>
        protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, SystemUserRequirement requirement) {
            if (context == null) { return Task.CompletedTask; }
            if (context.IsSystemUser()) {
                context.Succeed(requirement);
            }
            return Task.CompletedTask;
        }

        /// <summary>
        /// 
        /// </summary>
        public class SystemUserRequirement : IAuthorizationRequirement {
        }
    }
    /// <summary>
    /// 权限认证
    /// </summary>
    public class AuthorizationHandler : IAuthorizationHandler {
        private readonly IUserPermissionCheck permissionCheck;
        private readonly ApiCallerFactory callerFactory;
        /// <summary>
        /// 
        /// </summary>
        public const string ClaimUserType = "userType";
        /// <summary>
        /// 
        /// </summary>
        public const string ClaimUserName = "userName";
        /// <summary>
        /// 
        /// </summary>
        /// <param name="permissionCheck"></param>
        /// <param name="callerFactory"></param>
        public AuthorizationHandler(IUserPermissionCheck permissionCheck, ApiCallerFactory callerFactory) {
            this.permissionCheck = permissionCheck;
            this.callerFactory = callerFactory;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="context"></param>
        /// <returns></returns>
        async Task IAuthorizationHandler.HandleAsync(AuthorizationHandlerContext context) {
            switch (context.Resource) {
                case Microsoft.AspNetCore.Http.DefaultHttpContext dContext: {
                        dContext.Request.RouteValues.ToString();
                        if (!dContext.Request.RouteValues.TryGetValue("controller", out var controller)) {
                            context.Fail();
                            break;
                        }

                        if (!dContext.Request.RouteValues.TryGetValue("action", out var action)) {
                            context.Fail();
                            break;
                        }

                        if (context.User.Identity?.IsAuthenticated == true
                            //&& dContext.Items.TryGetValue(CommonConsts.ConstName_TOKEN_SECRETE, out object tokenSecret)
                            ) {
                            if (!context.User.IsSystemUser()
                                && !await UserPermissionAsync(context.User.Identity.Name, controller?.ToString(), action?.ToString())) {
                                context.Fail();
                            }
                        }
                        break;
                    }
                case AuthorizationFilterContext mvcContext: {
                        if (!mvcContext.RouteData.Values.TryGetValue("controller", out var controller)) {
                            context.Fail();
                        }
                        if (!mvcContext.RouteData.Values.TryGetValue("action", out var action)) {
                            context.Fail();
                        }
                        if (context.User.Identity?.IsAuthenticated == true
                            //&& mvcContext.HttpContext.Items.TryGetValue(CommonConsts.ConstName_TOKEN_SECRETE, out object tokenSecret)
                            ) {
                            if (!context.User.IsSystemUser()
                                && !await UserPermissionAsync(context.User.Identity.Name, controller?.ToString(), action?.ToString())) {
                                context.Fail();
                            }
                        }

                        break;
                    }
            }

            //return Task.CompletedTask;
        }
        async Task<bool> UserPermissionAsync(string? strUserId, string? controller, string? action) {
            var userId = int.TryParse(strUserId, out var tint) ? tint : 0;
            if (permissionCheck != null) {
                var r1 = permissionCheck.CheckUserPermission(userId, controller, action);
                //Console.WriteLine($"CheckUserPermission {userId}: {controller}/{action} = {r1}");
                return r1;
            }
            var r = await callerFactory.GetTokenService()
                                 .PostResultAsync<object>("user/CheckUserPermission",
                                                          body: new { UserId = userId, Controller = controller, Action = action });
            Console.WriteLine($"User/CheckUserPermission {strUserId}: {controller}/{action} = {r.Success == true}");
            if (r.Success == true) {
                return await Task.FromResult(true);
            }
            return await Task.FromResult(false);
        }

        /// <summary>
        /// 
        /// </summary>
        public class UserPermissionCheck : IUserPermissionCheck {
            private readonly ApiCallerFactory callerFactory;
            /// <summary>
            /// 
            /// </summary>
            /// <param name="callerFactory"></param>
            public UserPermissionCheck(ApiCallerFactory callerFactory) {
                this.callerFactory = callerFactory;
            }
            /// <inheritdoc/>
            public bool CheckUserPermission(int userId, string? controller, string? action, string permissionKey = "A") {
                var r = callerFactory.GetTokenService()
                                     .PostResultAsync<object>("user/CheckUserPermission",
                                                              body: new { UserId = userId, Controller = controller, Action = action })
                                     .Result;
                Console.WriteLine($"User/CheckUserPermission {userId}: {controller}/{action} = {r.Success == true}");
                return r.Success == true;
            }
        }
        /// <summary>
        /// 
        /// </summary>
        public class CheckUserPermissionHandler : IUserPermissionCheck {
            private readonly Func<string, MySqlParameter[], int> getCount;
            /// <summary>
            /// 
            /// </summary>
            /// <param name="GetCount"></param>
            public CheckUserPermissionHandler(Func<string, MySqlParameter[], int> GetCount) {
                getCount = GetCount;
            }
            /// <inheritdoc/>
            public bool CheckUserPermission(int userId, string? controller, string? action, string permissionKey = "A")
                => AuthorizationHandler.CheckUserPermission(getCount, userId, controller, action, permissionKey);
        }
        private static readonly ConcurrentDictionary<string, UserPermissionItem> USER_PREMISSION_LIST = new ConcurrentDictionary<string, UserPermissionItem>();
        private static readonly object checkPermissonObj = new();
        /// <summary>
        /// 
        /// </summary>
        /// <param name="GetCount"></param>
        /// <param name="userId"></param>
        /// <param name="controller"></param>
        /// <param name="action"></param>
        /// <param name="permissionKey"></param>
        /// <returns></returns>
        public static bool CheckUserPermission(Func<string, MySqlParameter[], int> GetCount,
                                               int userId,
                                               string? controller,
                                               string? action,
                                               string permissionKey = "A") {
            if (string.IsNullOrEmpty(controller) || string.IsNullOrEmpty(action))
                return false;
            var key = $"{controller}/{action}:{userId}:{permissionKey}".MD5Encrypt();
            if (USER_PREMISSION_LIST.TryGetValue(key, out var result) && result.ExpireTime > DateTime.UtcNow) {
                return result.Data;
            }
            var sqlParameters = new[] {
                new MySqlParameter("@UserID", userId),
                new MySqlParameter("@Controller", controller),
                new MySqlParameter("@Action", action),
                new MySqlParameter("@PermissionKey", permissionKey)
            };
            lock (checkPermissonObj) {
                //校验action是否需要检验
                if (GetCount("SELECT COUNT(0) `COUNT` FROM SYS_MODULE WHERE CONTROLLER = @Controller AND ACTION = @Action ", sqlParameters) < 1) {
                    USER_PREMISSION_LIST[key] = new UserPermissionItem {
                        Data = true,
                        ExpireTime = DateTime.UtcNow.AddMinutes(5),
                    };
                    return true;
                }
                var sql = "SELECT count(0) `COUNT` FROM user_role ur " +
                        "JOIN role_permission rp ON ur.ROLE_ID = rp.ROLE_ID " +
                        "JOIN sys_module sm on rp.MODULE_ID = sm.MODULE_ID " +
                        "JOIN sys_permission sp on rp.PERMISSION_ID = sp.PERMISSION_ID " +
                        "WHERE ur.USER_ID = @UserID AND sm.CONTROLLER = @Controller AND sm.ACTION = @Action " +
                        "AND sp.PERMISSION_KEY = @PermissionKey";
                //var exists = DbContext.COUNT_TABLE.GetCount(sql, 0, sqlParameters) > 0;
                result = new UserPermissionItem {
                    Data = GetCount(sql, sqlParameters) > 0,
                    ExpireTime = DateTime.UtcNow.AddDays(1),
                };
            }
            USER_PREMISSION_LIST[key] = result;
            return result.Data;
        }
        struct UserPermissionItem {
            public DateTime ExpireTime { get; set; }
            public bool Data { get; set; }
        }
    }
    /// <summary>
    /// 
    /// </summary>
    public interface IUserPermissionCheck {
        /// <summary>
        /// 
        /// </summary>
        /// <param name="userId"></param>
        /// <param name="controller"></param>
        /// <param name="action"></param>
        /// <param name="permissionKey"></param>
        /// <returns></returns>
        bool CheckUserPermission(int userId, string? controller, string? action, string permissionKey = "A");
    }
}
